/***************************************************************************** 
* 
* Search and replace on selected text using javascript regular expressions.
* The 1st line of the selected text is the 1 arg passed to String.replace().
* Lines 2 (or more) are passed as the 2nd arg, either as a replacement string
* or as a function.
* 
* The delimiters in the 1st selected line are optional.  By default the i and g
* flags are added. If the // regex literal delimiters are used you can supply 
* i and/or g.
* 
* The 2nd selected line is the replacement text.  Use heredoc format for a 
* a multi-line replacement:
*   /foo/g
*   <<<TEXT_TO_USE
*   This is a multi-
*   line replacement.
*   TEXT_TO_USE
*
* if the <<< is followed by a '!' the replacement is evaled so a function can
* be passed to the 2nd arg of String.replace():
*   /(foo)/g
*   <<<!FUNC
*   function(entire_match, m1){
*     return m1.toUpperCase();
*   }
*   !FUNC
* 
* If the function declaration is ommitted it is provided:
*   /(foo)/g
*   <<<!XYZZY
*   return $1.toUpperCase();
*   !XYZZY
*
* In this case the following local vars are created in the function:
*   $1,..$9   The parenthesized substrings (if any) from the regex
*   $i        The nth match.  Indexed for each match.
*   global    A global object persistent between calls to the func created
*   _         A global object persistent between calls to the func created
* 
****************************************************************************** 
* EXAMPLES (move this to a README?)
****************************************************************************** 
*
* (1) Replace all triple (or more) letters with a double letter:
* 
* INPUT:
* 
*   (\w)\1{2,}
*   $1
*   Loook at me I'm as happpppy as a kitten
*   in a treEe.
* 
* OUTPUT:
* 
*   Look at me I'm as happy as a kitten
*   in a tree.
* 
* (2) Same as last, case sensitive:
* 
* INPUT:
* 
*   /(\w)\1{2,}/g
*   $1$1
*   Loook at me I'm as happpppy as a kitten
*   in a treEe.
* 
* OUTPUT:
* 
*   Look at me I'm as happy as a kitten
*   in a treEe.
* 
* (3) Multi-line replacement can be passes using heredoc format:
* 
* INPUT:
* 
*   (<[^>]+>)(<\/[^>]+>)
*   <<<EOF
*   $1
*   	Add Some text
*   	in each empty tag
*   $2
*   EOF
*   <div></div>
*   <b></b>
* 
* OUTPUT:
* 
*   <div>
*   	 Add Some text
*   	 in each empty tag
*   </div>
*   <b>
*   	 Add Some text
*   	 in each empty tag
*   </b>
* 
* (4) Passing in a function:
* 
* INPUT:
* 
*   (line) *(\d+)
*   <<<!EOF
*   function(match, s, num){
*     while(num.length < 5) num = "0" + num;
*     return num + " " + s.toUpperCase();
*   }
*   !EOF
*   liNe 1
*   liNe 2
*   liNe    3
*   liNe 4
*   LINE 4
* 
* OUTPUT:
* 
*   00001 LINE
*   00002 LINE
*   00003 LINE
*   00004 LINE
*   00004 LINE
* 
* (5) A function, along with $i and $1,...$n, is created for us:
*
* INPUT:
*
*   (\w+)
*   <<<!BAR
*   return !( $i % 3 ) ? $1.toUpperCase() : $1;
*   !BAR
*   one two three
*     four five
*   six seven eight nine
*   ten eleven
*
* OUTPUT:
*   ONE two three
*     FOUR five
*   six SEVEN eight nine
*   TEN eleven
* 
* (6) A contrived example:
* 
* INPUT:
* 
*   (.*)\b\s+\b(\d+)
*   <<<!EOF
*     if(!global.n) global.n = 1;           // Create global counter
*     $2 = +$2;                             // $2 (and $1) is initialized for us.
*     
*     function pad(s){                      // Declare an inner function
*       s += '';
*       while(s.length < 5) s = "0" + s;
*       return s;
*     }
*     
*     var newlns = [];                       // Add 'missing' lines
*     for( ; global.n < $2; global.n++ ){
*       newlns.push( pad( global.n ) + "\n" );
*     }
*     global.n++;
*     
*     $1 = ($i % 2)                         // Every other match
*        ? $1.toUpperCase()
*        : $1.toLowerCase();
*     $2 = pad($2);
*     
*     return newlns.join("") + $2 + " " + $1 + "."
*   !EOF
*   A Line of TEXT 1
*   B Line of TEXT      3
*   C Line of TEXT 5
*   C.2 Line of TEXT 6
*   d Line of TEXT 12
* 
* OUTPUT:
*   
*   00001 a line of text.
*   00002
*   00003 B LINE OF TEXT.
*   00004
*   00005 c line of text.
*   00006 C.2 LINE OF TEXT.
*   00007
*   00008
*   00009
*   00010
*   00011
*   00012 d line of text.
*   
*****************************************************************************/ 

function Main(){
  // BUG: Can a passed in replacement func can clobber vars?  Create a scope 
  // to contain?
	var CRLF = "\r\n";
	var global = {};
	var _ = {};
	var $i = -1;
	
	var app = Application;
	var doc = app.ActiveDocument;
	var txt = doc.SelText;

	var lines = doc.SelText.split(CRLF);
	switch( lines.length ){
		case 0: case 1:
			app.MessageBox("No regex defined", "RegExp Search and Replace", 16);
			return;
		case 2:
			app.MessageBox("No replacement defined", "RegExp Search and Replace", 16);
			return;
	}
	
	var sbof = "", seof = "";
	var sRe = lines.shift().replace(/ *\//, "/");
	
	var re;
	try{
		if( sRe.indexOf("/") !== 0 ){
			re = eval("/" + sRe + "/gi");
		} else{
			re = eval(sRe);
		}
	} catch(e){
		app.MessageBox(e.description, "Error in RegExp", 16);
		return;
	}
	
	// TODO: equal indentation for multiline replacement.
	
	var s = lines.shift();
	
	var eof = s.match(/^ *<<< *([\w!]+) *$/);
	if( eof && eof[1] ){
		sbof = "<<<" + eof[1] + CRLF;
		seof = CRLF + eof[1];
		var reeof = new RegExp("^ *" + eof[1] + " *$");
		var line = true;
		s = [];
		while( line !== void 0 && lines.length > 0 && line !== eof[1] ){
			if(line !== true) s.push(line);
			line = lines.shift();
			if(lines.length == 0){
				app.MessageBox("Could not find end of here doc.  Looking for '" + eof[1] + "'", "Error in Here Doc", 16);
				return;
			}
		}
	}
	s = s.join ? s.join(CRLF) : s;
	var sfn = s;
	if( sbof.indexOf("<<<!") === 0 ){
		try{
			if( sfn.match( /\s*function\s*\(/ ) ){
				eval("sfn = " + s);
			} else{
				eval("sfn = function(){\
					var $ = arguments; $i++;\
					var $1='$1',$2='$2',$3='$3',$4='$4',$5='$5',$6='$6',$7='$7',$8='$8',$9='$9';\
					for(var i=1; i<$.length-2; i++){\
						if($[i] === void 0) continue;\
						eval( 'var $' + i + '=\"' + $[i] + '\"' );\
					}\
					" + sfn + "\
					;}"
				);
			}
			if(typeof sfn != "function"){
				app.MessageBox("Code doesn't eval to a function", "Code doesn't eval to a function", 16);
				return;
			}
		} catch(ex){
			app.MessageBox(ex.description + "\n\n" + sfn, "Error in Function", 16);
			return;
		}
	}
	
	var text = lines.join(CRLF)
	
	try{	
		text = text.replace( re, sfn );
	} catch(ex){
		app.MessageBox(ex.description, "Error calling replace()", 16);
		return;
	}
	
	doc.SelText = sRe + CRLF + sbof + s + seof + CRLF  + text;
}
